/*
 * Copyright (C) 2010 Zhenya Leonov
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jpal.collect;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NavigableSet;
import java.util.NoSuchElementException;

import com.google.common.base.Function;
import com.google.common.collect.ForwardingListIterator;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.PeekingIterator;
import com.google.common.collect.UnmodifiableIterator;
import com.google.common.collect.UnmodifiableListIterator;

/**
 * More static utility methods which operate on or return {@link Iterator}s.
 * 
 * @author Zhenya Leonov
 * @see Iterators
 */
public final class MoreIterators {

	private MoreIterators() {
	}

	private static final ListIterator<Object> EMPTY_LIST_ITERATOR = new ListIterator<Object>() {

		@Override
		public boolean hasNext() {
			return false;
		}

		@Override
		public boolean hasPrevious() {
			return false;
		}

		@Override
		public Object next() {
			throw new NoSuchElementException();
		}

		@Override
		public int nextIndex() {
			return 0;
		}

		@Override
		public Object previous() {
			throw new NoSuchElementException();
		}

		@Override
		public int previousIndex() {
			return -1;
		}

		@Override
		public final void remove() {
			throw new IllegalStateException();
		}

		@Override
		public final void add(Object e) {
			throw new UnsupportedOperationException();
		}

		@Override
		public final void set(Object e) {
			throw new IllegalStateException();
		}
	};

	private static final PeekingIterator<Object> EMPTY_PEEKING_ITERATOR = new PeekingIterator<Object>() {

		@Override
		public Object next() {
			throw new NoSuchElementException();
		}

		@Override
		public Object peek() {
			throw new NoSuchElementException();
		}

		@Override
		public boolean hasNext() {
			return false;
		}

		@Override
		public final void remove() {
			throw new IllegalStateException();
		}
	};

	/**
	 * Adapts an {@code Iterator} to behave like an {@code Iterable} for
	 * convenient use in a for-each loop.
	 * <p>
	 * <b>Warning: the returned {@code Iterable} does not behave as a general
	 * purpose {@code Iterable} as it supports only a single {@code Iterator}.
	 * Invoking the {@link Iterable#iterator()} method to obtain a subsequent
	 * iterator throws an {@code IllegalStateException}.</b>
	 * 
	 * @param iterator
	 *            the specified iterator
	 * @return an {@code Iterable} which returns the specified {@code Iterator}
	 *         on the first call to {@link Iterable#iterator()}
	 */
	public static <E> Iterable<E> asIterable(final Iterator<E> iterator) {
		checkNotNull(iterator);

		return new Iterable<E>() {
			private boolean first = true;

			@Override
			public Iterator<E> iterator() {
				if (!first)
					throw new IllegalStateException();
				first = false;
				return iterator;
			}
		};
	}

	/**
	 * Combines multiple list iterators into a single list iterator. All but the
	 * first list iterators are rewound to the beginning.
	 * <p>
	 * The returned list iterator is unmodifiable.
	 * 
	 * @param inputs
	 *            the source list iterators
	 * @return an {@code UnmodifiableListIterator} consisting of specified list
	 *         iterators
	 */
	// this method is stupid and makes no sense
	@SuppressWarnings("unchecked")
	public static <E> UnmodifiableListIterator<E> concat(
			final ListIterator<? extends E>... inputs) {
		checkNotNull(inputs);

		if (inputs.length == 0)
			return (UnmodifiableListIterator<E>) unmodifiable(emptyListIterator());

		for (int i = 1; i < inputs.length; i++) {
			final ListIterator<? extends E> li = inputs[i];
			while (li.hasPrevious())
				li.previous();
		}

		return new UnmodifiableListIterator<E>() {

			private int count = 0;
			private int index = inputs[0].nextIndex();

			@Override
			public boolean hasNext() {
				while (count < inputs.length) {
					if (inputs[count].hasNext())
						return true;
					count++;
				}
				count--;
				return false;
			}

			@Override
			public boolean hasPrevious() {
				while (count >= 0) {
					if (inputs[count].hasPrevious())
						return true;
					count--;
				}
				count = 0;
				return false;
			}

			@Override
			public E next() {
				if (hasNext()) {
					index++;
					return inputs[count].next();
				}
				throw new NoSuchElementException();
			}

			@Override
			public int nextIndex() {
				return index;
			}

			@Override
			public E previous() {
				if (hasPrevious()) {
					index--;
					return inputs[count].previous();
				}
				throw new NoSuchElementException();
			}

			@Override
			public int previousIndex() {
				return index - 1;
			}
		};
	}

	/**
	 * Returns an immutable empty {@code ListIterator}.
	 * 
	 * @return an immutable empty {@code ListIterator}
	 */
	@SuppressWarnings("unchecked")
	public static <E> ListIterator<E> emptyListIterator() {
		return (ListIterator<E>) EMPTY_LIST_ITERATOR;
	}

	/**
	 * Returns an immutable empty {@code PeekingIterator}.
	 * 
	 * @return an immutable empty {@code PeekingIterator}
	 */
	@SuppressWarnings("unchecked")
	public static <E> PeekingIterator<E> emptyPeekingIterator() {
		return (PeekingIterator<E>) EMPTY_PEEKING_ITERATOR;
	}

	/**
	 * Decorates the specified {@code Iterator} to behave like a
	 * {@code ListIterator} by maintaining a {@code List} of all elements
	 * returned from calls to {@code next()}.
	 * <p>
	 * The {@code remove()}, {@code set(E)}, and {@code add(E)} operations are
	 * not supported. Modifying the underlying {@code Iterator} yields
	 * unpredictable results.
	 * 
	 * @param iterator
	 *            the underlying {@code Iterator}
	 * @return a {@code UnmodifiableListIterator} backed by {@code iterator}
	 */
	public static <E> UnmodifiableListIterator<E> listIterator(
			final Iterator<E> iterator) {
		checkNotNull(iterator);
		return new UnmodifiableListIterator<E>() {
			private final List<E> list = Lists.newArrayList();
			private int index = -1;

			@Override
			public boolean hasNext() {
				if (index == list.size() - 1)
					return iterator.hasNext();
				else
					return true;
			}

			@Override
			public E next() {
				E nextElement;
				if (index == list.size() - 1) {
					nextElement = iterator.next();
					list.add(nextElement);
					index++;
				} else
					nextElement = list.get(++index);
				return nextElement;
			}

			@Override
			public boolean hasPrevious() {
				return index >= 0;
			}

			@Override
			public E previous() {
				if (index < 0)
					throw new NoSuchElementException();
				return list.get(index--);
			}

			@Override
			public int nextIndex() {
				return index + 1;
			}

			@Override
			public int previousIndex() {
				return index;
			}
		};
	}

	/**
	 * Returns an {@code Iterator} over the specified {@code Collection} in
	 * reverse sequential order. If the specified collection is an instance of
	 * {@code Deque} or {@code NavigableSet} the iterator is obtained by
	 * invoking the {@code descendingIterator()} method, else an unmodifiable
	 * iterator is returned by calling the {@code reverseOrder(Iterator)}
	 * method.
	 * 
	 * @param c
	 *            the specified collection
	 * @return an iterator over the specified collection in reverse sequential
	 *         order
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <E> Iterator<E> reverseOrder(final Collection<? extends E> c) {
		checkNotNull(c);
		if (c instanceof Deque<?>)
			return ((Deque) c).descendingIterator();
		if (c instanceof NavigableSet<?>)
			return ((NavigableSet) c).descendingIterator();
		else
			return reverseOrder(c.iterator());

	}

	/**
	 * Returns an {@code UnmodifiableIterator} which reverses the iteration
	 * order of the specified {@code Iterator}. The underlying iterator must be
	 * non-blocking and finite, because it is necessary to iterate through all
	 * its elements on startup.
	 * 
	 * @param iterator
	 *            the underlying {@code Iterator}
	 * @return a reverse view of {@code iterator}
	 */
	public static <E> UnmodifiableIterator<E> reverseOrder(
			final Iterator<? extends E> iterator) {
		checkNotNull(iterator);
		final Deque<E> stack = Lists.newLinkedList();

		while (iterator.hasNext())
			stack.push(iterator.next());

		return new UnmodifiableIterator<E>() {
			@Override
			public boolean hasNext() {
				return !stack.isEmpty();
			}

			@Override
			public E next() {
				return stack.pop();
			}
		};
	}

	/**
	 * Returns a {@code ListIterator} that applies the specified
	 * {@code Function} to each element of the given list iterator.
	 * <p>
	 * The returned list iterator does not support {@code add(E)} and
	 * {@code set(E)} but does support {@code remove()} if the given list
	 * iterator does.
	 * 
	 * @param <E>
	 *            the type of elements traversed by the given list iterator
	 * @param <T>
	 *            the type of output of the specified function
	 * @param fromIterator
	 *            the given list iterator
	 * @param function
	 *            the specified function
	 * @return a list iterator that applies {@code function} to each element of
	 *         {@code fromIterator}
	 */
	public static <E, T> ListIterator<T> transform(
			final ListIterator<E> fromIterator,
			final Function<? super E, ? extends T> function) {
		checkNotNull(fromIterator);
		checkNotNull(function);
		return new ForwardingListIterator<T>() {
			@Override
			public T next() {
				return function.apply(fromIterator.next());
			}

			@Override
			public void add(T e) {
				throw new UnsupportedOperationException();
			}

			@Override
			public T previous() {
				return function.apply(fromIterator.previous());
			}

			@Override
			public void set(T e) {
				throw new UnsupportedOperationException();
			}

			@SuppressWarnings("unchecked")
			@Override
			// ok since type T is only used in next, previous, add, and set
			protected ListIterator<T> delegate() {
				return (ListIterator<T>) fromIterator;
			}
		};
	}

	/**
	 * Decorates the specified {@code ListIterator} to prevent
	 * {@link ListIterator#remove() remove()}, {@link ListIterator#add(Object)
	 * add(E)}, and {@link ListIterator#set(Object) set(E)}.
	 * 
	 * @param listIterator
	 *            the underlying {@code ListIterator}
	 * @return an unmodifiable view of {@code listIterator}
	 */
	public static <E> UnmodifiableListIterator<E> unmodifiable(
			final ListIterator<? extends E> listIterator) {
		checkNotNull(listIterator);
		return new UnmodifiableListIterator<E>() {
			@Override
			public boolean hasNext() {
				return listIterator.hasNext();
			}

			@Override
			public boolean hasPrevious() {
				return listIterator.hasPrevious();
			}

			@Override
			public E next() {
				return listIterator.next();
			}

			@Override
			public int nextIndex() {
				return listIterator.nextIndex();
			}

			@Override
			public E previous() {
				return listIterator.previous();
			}

			@Override
			public int previousIndex() {
				return listIterator.previousIndex();
			}
		};
	}

	/**
	 * Decorates the specified {@code PeekingIterator} to prevent
	 * {@link PeekingIterator#remove() remove()}.
	 * 
	 * @param peekingIterator
	 *            the underlying {@code PeekingIterator}
	 * @return an unmodifiable view of {@code peekingIterator}
	 */
	public static <E> UnmodifiablePeekingIterator<E> unmodifiable(
			final PeekingIterator<? extends E> peekingIterator) {
		checkNotNull(peekingIterator);
		return new UnmodifiablePeekingIterator<E>() {

			@Override
			public E peek() {
				return peekingIterator.peek();
			}

			@Override
			public E next() {
				return peekingIterator.next();
			}

			@Override
			public boolean hasNext() {
				return peekingIterator.hasNext();
			}
		};
	}

}